package Code;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;



public class Environment {

	private static final int ATTEMPT_THRESHOLD = 750;
	private int CLOSE_ENOUGH = 50;
	private int SENSOR_WIDTH = 100;
	
	private double PREDICTION_THRESHOLD = .95;
	
	private int attempts = 0;
	
	private List<Tank> myTanks;
	private List<Obstacle> obstacles;
	private List<EnemyTank> otherTanks;
	private List<Base> bases;
	private List<Flag> flags;
	private int worldSize;
	private Team myTeam;
	private List<Point> traversalPoints = new ArrayList<>();
	
	private Map<String, Point> obstaclePoints = new HashMap<String, Point>();
	
	private Grid displayGrid;
	private OccGrid occGrid;

	private BZRClient client;
	private Map<String, String> constants;
	private boolean isTraversing = false;
	
	public Environment(final BZRClient client, final boolean isTraversing) {
		this.isTraversing = isTraversing;
		this.client = client;
		this.refresh();
		obstacles = new ArrayList<>();
		
		this.displayGrid = new Grid(worldSize, worldSize, SENSOR_WIDTH);
		this.occGrid = client.getOccGrid(0);
		
		buildTraversalPoints();
		//GnuPlotGenerator.generateOutput4GnuPlot(worldSize, obstacles);
	}
	
	public void refresh() {
		myTanks = client.getTanks();
//		if(!isTraversing) 
		obstacles = client.getObstacles();
		bases = client.getBases();
		otherTanks = client.getOtherTanks();
		constants = client.getConstants();
		flags = client.getFlags();
		worldSize = Integer.parseInt(constants.get("worldsize"));
		myTeam = Team.valueOf(constants.get("team").toUpperCase());
	}
	
	private void buildTraversalPoints() {
		for(int x = -1*(worldSize/2 -25); x < ((worldSize/2 -25) + 1); x+=55) {
			int modifier = (x%2 == 0) ? -1 : 1;
			
			traversalPoints.add(new Point(x, modifier*(worldSize/2 - 40)));
			traversalPoints.add(new Point(x, 0));
			traversalPoints.add(new Point(x, modifier*(-1)*(worldSize/2 - 40)));
		}
		for(int y = -1*(worldSize/2 -25); y < ((worldSize/2 -25) + 1); y+=55) {
			int modifier = (y%2 == 0) ? -1 : 1;
			
			traversalPoints.add(new Point(modifier*(worldSize/2 - 40), y));
			traversalPoints.add(new Point(0, y));
			traversalPoints.add(new Point(modifier*(-1)*(worldSize/2 - 40), y));
		}
	}
	
	public void updateGrid(int tankId) {
		occGrid = client.getOccGrid(tankId);
		if(occGrid == null) return;
		
		for(int i = 0; i < occGrid.getXLength(); i++) {
			int x = (int) (i + occGrid.getLocation().getX());
			x += worldSize/2;

			for(int j = 0; j < occGrid.getYLength(); j++) {
				int y = (int) (j + occGrid.getLocation().getY());
				//X + worldsize/2
				//-1(Y) + worldsize/2
				y = -1*y + worldSize/2;
				if(y < displayGrid.getWorldX() && x < displayGrid.getWorldY()) {
					double oldBelief = displayGrid.getValue(x, y);
					double newBelief = CalcStatistics.calcBeliefOcc(oldBelief, occGrid.getHit(i, j), getTruePositive(), getTrueNegative());
					final Point p = new Point(x - worldSize/2,  y- worldSize/2);
					if(oldBelief < PREDICTION_THRESHOLD && newBelief >= PREDICTION_THRESHOLD) {
//						obstacles.add(new Obstacle(p));
						obstaclePoints.put(p.toString(), p);
					}
					if(newBelief < PREDICTION_THRESHOLD) {
//						obstacles.remove(p);
						obstaclePoints.remove(p.toString());
					}
					displayGrid.updateValue(new Point(x, y), newBelief);
				}
			}
		}
		displayGrid.update(myTanks.get(0), getTraversalPoint(myTanks.get(0)));
	}

	public void printObstaclePoints() {
		for(Point point : obstaclePoints.values()) {
			System.out.print(point.toString());
		}
		System.out.println();
	}
	
	public Grid getGrid() {
		return displayGrid;
	}
	
	public OccGrid getOccGrid() {
		return occGrid;
	}
	
	private double getTruePositive() {
		return Double.parseDouble(getConstant("truepositive"));
	}
	
	private double getTrueNegative() {
		return Double.parseDouble(getConstant("truenegative"));
	}
	
	public List<Tank> getTanks() {
		return myTanks;
	}
	
	public List<Obstacle> getObstacle() {
		return obstacles;
	}
	
	public List<EnemyTank> getOtherTanks() {
		return otherTanks;
	}
	
	public List<Base> getBases() {
		return bases;
	}
	
	public int getWorldSize() {
		return worldSize;
	}
	
	public Team getMyTeam() {
		return myTeam;
	}
	
	public String getConstant(final String key) {
		return constants.get(key);
	}

	public Point getPotential(int tankId) {
		this.refresh();
		final Tank tank = myTanks.get(tankId);
		
		Point point = null; 
		if(isTraversing) {
			point = getTraversalPoint(tank);
			return CalculateTraversalPotentials.calcPotentialPt(tank.getLocation(), point, new ArrayList<Point>(obstaclePoints.values()), worldSize);
//			return CalculatePotentials.calcPotentialPt(tank.getLocation(), point, obstacles, worldSize);
		} else {
			point =  getClosestFlag(tank).getPoint();
			return CalculatePotentials.calcPotentialPt(tank.getLocation(), point, obstacles, worldSize);
		}
		
	}
	
	private Point getTraversalPoint(final Tank tank) {
		System.out.println(attempts);
		attempts++;
		if(traversalPoints.isEmpty()) {
			buildTraversalPoints();
		}
		final Point point = traversalPoints.get(0);
		if(point.calcDistance(tank.getLocation()) < CLOSE_ENOUGH) {
			attempts = 0;
			traversalPoints.remove(point);
		} else if(attempts > ATTEMPT_THRESHOLD) {
			attempts = 0;
			traversalPoints.remove(point);
			traversalPoints.add(point);
			return traversalPoints.get(0);
		} 
		return point;
	}
	
	public Point getPotentialForMyBase(int tankId) {
		this.refresh();
		final Tank tank = myTanks.get(tankId);
		
		return CalculatePotentials.calcPotentialPt(tank.getLocation(), getMyBaseCenter(tank), obstacles, worldSize);
	}

	private Point getMyBaseCenter(final Tank tank) {
		for(final Base base : bases) {
			if(myTeam.equals(base.getTeam())) {
				return base.getCenter();
			}
		}
		return Point.ZERO;
	}
	
	private Flag getClosestFlag(final Tank tank) {
		Flag closest = flags.get(0);
		double minimimDistance = Double.MAX_VALUE;
		for(final Flag flag : flags) {
			if(flag.getTeam() != myTeam) {
				double temp = tank.getLocation().calcDistance(flag.getPoint());
				if(temp <= minimimDistance) {
					minimimDistance = temp;
					closest = flag;
				}
			}
		}
		return closest;
	}
}
